package com.ozo.cloud.gen.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ozo.cloud.common.base.constant.CommonConstant;
import com.ozo.cloud.common.base.constant.CommonFieldConstant;
import com.ozo.cloud.common.base.model.BaseBatchDTO;
import com.ozo.cloud.common.base.model.BasePage;
import com.ozo.cloud.common.base.model.PageResult;
import com.ozo.cloud.common.base.util.NamingConventionUtils;
import com.ozo.cloud.common.core.exception.ServiceException;
import com.ozo.cloud.common.database.util.SortUtils;
import com.ozo.cloud.gen.constant.GenerationType;
import com.ozo.cloud.gen.entity.GenTable;
import com.ozo.cloud.gen.entity.GenTableColumn;
import com.ozo.cloud.gen.exception.GenExceptionEnum;
import com.ozo.cloud.gen.mapper.DbMapper;
import com.ozo.cloud.gen.mapper.GenTableMapper;
import com.ozo.cloud.gen.pojo.bo.GenTableBO;
import com.ozo.cloud.gen.pojo.bo.GenTableColumnBO;
import com.ozo.cloud.gen.pojo.dto.*;
import com.ozo.cloud.gen.pojo.vo.GenTableVO;
import com.ozo.cloud.gen.service.CodeGeneratorService;
import com.ozo.cloud.gen.service.IGenTableColumnService;
import com.ozo.cloud.gen.service.IGenTableService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ozo.cloud.gen.util.sql.MySQLCreateTableBuilder;
import com.ozo.cloud.gen.util.TableColumnInitUtil;
import com.ozo.cloud.gen.util.translate.YoudaoTranslateUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.sql.DataSource;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * <p>
 * 表配置 服务实现类
 * </p>
 *
 * @author qiangesoft
 * @since 2024-11-12
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class GenTableServiceImpl extends ServiceImpl<GenTableMapper, GenTable> implements IGenTableService {

    private final DbMapper dbMapper;

    private final DataSource dataSource;

    private final IGenTableColumnService genTableColumnService;

    private final CodeGeneratorService codeGeneratorService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void importTable(GenTableImportDTO tableImportDTO) {
        // 配置存在性校验
        List<String> tables = tableImportDTO.getTables();
        LambdaQueryWrapper<GenTable> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(GenTable::getTableName, tables);
        Long count = baseMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new ServiceException(GenExceptionEnum.TABLE_CONFIG_EXISTS);
        }

        String dbType = this.getDbType();
        if (dbType == null) {
            throw new ServiceException(GenExceptionEnum.IMPORT_FAIL);
        }

        // 表
        List<GenTableBO> allTableList = dbMapper.listTable(dbType, null);
        List<GenTableBO> tableBOList = allTableList.stream().filter(e -> tableImportDTO.getTables().contains(e.getTableName())).toList();
        List<String> tableNames = tableBOList.stream().map(GenTableBO::getTableName).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(tableNames)) {
            throw new ServiceException(GenExceptionEnum.DB_TABLE_NOT_EXISTS);
        }

        // 表字段
        List<GenTableColumnBO> tableColumnBOList = dbMapper.listTableColumn(dbType, tableNames);
        Map<String, List<GenTableColumnBO>> tableColumnMap = tableColumnBOList.stream().collect(Collectors.groupingBy(GenTableColumnBO::getTableName));

        // 入库
        for (GenTableBO tableBO : tableBOList) {
            List<GenTableColumnBO> columnBOList = tableColumnMap.get(tableBO.getTableName());
            if (CollUtil.isEmpty(columnBOList)) {
                continue;
            }

            GenTable table = TableColumnInitUtil.initTable(tableBO);
            table.setTableType(GenerationType.IMPORT.getCode());
            Set<String> columnNames = columnBOList.stream().map(GenTableColumnBO::getColumnName).collect(Collectors.toSet());
            table.setExtendBaseEntity(columnNames.containsAll(Arrays.asList(CommonFieldConstant.BASE_COLUMN)));
            baseMapper.insert(table);

            List<GenTableColumn> tableColumnList = TableColumnInitUtil.initTableColumn(table.getId(), dbType, columnBOList);
            genTableColumnService.saveBatch(tableColumnList);
        }
    }

    /**
     * 获取数据库类型
     *
     * @return
     */
    private String getDbType() {
        try {
            return dataSource.getConnection().getMetaData().getDatabaseProductName();
        } catch (SQLException e) {
            return null;
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createTable(GenTableCreateDTO tableCreateDTO) {
        // 合法性校验
        this.checkParam(tableCreateDTO);

        // 配置存在性校验
        String tableName = tableCreateDTO.getTableName();
        LambdaQueryWrapper<GenTable> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(GenTable::getTableName, tableName);
        Long count = baseMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new ServiceException(GenExceptionEnum.TABLE_CONFIG_EXISTS);
        }

        String dbType = this.getDbType();
        if (dbType == null) {
            throw new ServiceException(GenExceptionEnum.CREATE_TABLE_FAIL);
        }

        // 表存在性校验
        List<GenTableBO> genTableBOList = dbMapper.listTable(dbType, tableName);
        if (CollUtil.isNotEmpty(genTableBOList)) {
            throw new ServiceException(String.format("表%s已存在", tableName));
        }

        // 生成建表语句
        Boolean extendBaseEntity = tableCreateDTO.getExtendBaseEntity();
        MySQLCreateTableBuilder builder = new MySQLCreateTableBuilder();
        builder.setTableName(tableName).setTableComment(tableCreateDTO.getTableComment());
        if (extendBaseEntity) {
            builder.addColumn(new MySQLCreateTableBuilder.Column("id", "id", "bigint", null, null, false, null, true, true));
        }
        List<GenTableColumnCreateDTO> columnList = tableCreateDTO.getColumnList();
        for (GenTableColumnCreateDTO column : columnList) {
            builder.addColumn(new MySQLCreateTableBuilder.Column(column.getColumnName(), column.getColumnComment(), column.getColumnType(), column.getDataLength(), column.getDecimalLength(), !column.getColumnNotNull(), column.getDefaultValue(), column.getColumnPk(), column.getColumnIncrement()));
        }
        if (extendBaseEntity) {
            builder.addColumn(new MySQLCreateTableBuilder.Column("create_user", "创建者", "bigint", null, null, true, null, false, false))
                    .addColumn(new MySQLCreateTableBuilder.Column("create_time", "创建时间", "datetime", null, null, true, null, false, false))
                    .addColumn(new MySQLCreateTableBuilder.Column("update_user", "更新者", "bigint", null, null, true, null, false, false))
                    .addColumn(new MySQLCreateTableBuilder.Column("update_time", "更新时间", "datetime", null, null, true, null, false, false));
        }
        String tableSql = builder.build();
        log.info(tableSql);

        // 执行建表语句
        try {
            this.doExecuteSql(tableSql);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new ServiceException(GenExceptionEnum.CREATE_TABLE_FAIL);
        }

        // 生成记录
        GenTable table = TableColumnInitUtil.initTable(BeanUtil.copyProperties(tableCreateDTO, GenTableBO.class));
        table.setTableType(GenerationType.CREATE.getCode());
        Set<String> columnNames = columnList.stream().map(GenTableColumnCreateDTO::getColumnName).collect(Collectors.toSet());
        table.setExtendBaseEntity(columnNames.containsAll(Arrays.asList(CommonFieldConstant.BASE_COLUMN)));
        baseMapper.insert(table);

        List<GenTableColumnBO> columnBOList = BeanUtil.copyToList(columnList, GenTableColumnBO.class);
        List<GenTableColumn> tableColumnList = TableColumnInitUtil.initTableColumn(table.getId(), dbType, columnBOList);
        genTableColumnService.saveBatch(tableColumnList);
    }

    /**
     * 执行SQL脚本
     *
     * @param sql
     */
    public void doExecuteSql(String sql) throws SQLException {
        Connection connection = DataSourceUtils.getConnection(dataSource);
        Statement statement = connection.createStatement();
        statement.execute(sql);
        statement.close();
        connection.close();
    }

    /**
     * 参数校验
     *
     * @param tableCreateDTO
     */
    private void checkParam(GenTableCreateDTO tableCreateDTO) {
        // 字段注释检查
        List<GenTableColumnCreateDTO> columnList = tableCreateDTO.getColumnList();
        Pattern pattern = Pattern.compile("^[\\u4E00-\\u9FFF]+$");
        for (GenTableColumnCreateDTO column : columnList) {
            String columnComment = column.getColumnComment();
            String columnName = column.getColumnName();
            if (StrUtil.isBlank(columnName)) {
                Matcher matcher = pattern.matcher(columnComment);
                if (!matcher.matches()) {
                    throw new ServiceException(GenExceptionEnum.COMMENT_MUST_BE_CHINESE);
                }
            }
        }

        // 处理字段
        for (GenTableColumnCreateDTO column : columnList) {
            String columnName = column.getColumnName();
            if (StrUtil.isBlank(columnName)) {
                try {
                    String transEn = YoudaoTranslateUtil.transEn(column.getColumnComment());
                    String replace = transEn.replace(CommonConstant.PLACE, CommonConstant.UNDERLINE);
                    column.setColumnName(NamingConventionUtils.camelToUnderline(replace));
                } catch (NoSuchAlgorithmException e) {
                    throw new ServiceException(GenExceptionEnum.COLUMN_TRANS_FAIL);
                }
            }
        }

        // 主键检查
        Boolean extendBaseEntity = tableCreateDTO.getExtendBaseEntity();
        long count = columnList.stream().filter(e -> Objects.nonNull(e.getColumnPk()) && e.getColumnPk()).count();
        if (extendBaseEntity && count > 0) {
            throw new ServiceException(GenExceptionEnum.EXTEND_NOT_NEED_PRI);
        }

        // 基类字段检查
        if (extendBaseEntity) {
            List<String> fieldList = Arrays.asList(CommonFieldConstant.BASE_COLUMN);
            long baseCount = columnList.stream().filter(e -> fieldList.contains(e.getColumnName())).count();
            if (baseCount > 0) {
                throw new ServiceException(GenExceptionEnum.EXTEND_NOT_NEED_BASE);
            }
        }

        // 字段重复性检查
        Set<String> columnNames = columnList.stream().map(GenTableColumnCreateDTO::getColumnName).collect(Collectors.toSet());
        if (columnNames.size() != columnList.size()) {
            throw new ServiceException(GenExceptionEnum.COLUMN_NOT_REPEAT);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void remove(BaseBatchDTO<Long> batchDTO) {
        List<Long> idList = batchDTO.getIdList();
        baseMapper.deleteBatchIds(idList);

        LambdaQueryWrapper<GenTableColumn> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(GenTableColumn::getTableId, idList);
        genTableColumnService.remove(queryWrapper);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void edit(GenTableDetailDTO saveDTO) {
        GenTable genTable = BeanUtil.copyProperties(saveDTO, GenTable.class);
        baseMapper.updateById(genTable);

        List<GenTableColumnDetailDTO> columnDTOList = saveDTO.getColumnList();
        List<GenTableColumn> tableColumnList = BeanUtil.copyToList(columnDTOList, GenTableColumn.class);
        for (int i = 0; i < tableColumnList.size(); i++) {
            tableColumnList.get(i).setOrderNum(i + 1);
        }
        genTableColumnService.updateBatchById(tableColumnList);
    }

    @Override
    public PageResult<GenTableVO> page(BasePage basePage, GenTableQueryDTO queryDTO) {
        Integer pageNum = basePage.getPageNum();
        Integer pageSize = basePage.getPageSize();

        QueryWrapper<GenTable> queryWrapper = new QueryWrapper<>();
        // 排序
        String order = basePage.getOrder();
        if (StrUtil.isBlank(order)) {
            queryWrapper.lambda().orderByDesc(GenTable::getId);
        } else {
            SortUtils.setSortCondition(queryWrapper, basePage.getProp(), order);
        }
        // 条件
        queryWrapper.lambda()
                .like(StrUtil.isNotBlank(queryDTO.getTableName()), GenTable::getTableName, queryDTO.getTableName())
                .like(StrUtil.isNotBlank(queryDTO.getTableComment()), GenTable::getTableComment, queryDTO.getTableComment());
        Page<GenTable> page = baseMapper.selectPage(new Page<>(pageNum, pageSize), queryWrapper);

        PageResult<GenTableVO> pageResult = new PageResult<>();
        pageResult.setPageNum(pageNum);
        pageResult.setPageSize(pageSize);
        pageResult.setPage(page.getPages());
        pageResult.setTotal(page.getTotal());
        List<GenTableVO> tableVOList = BeanUtil.copyToList(page.getRecords(), GenTableVO.class);
        pageResult.setResults(tableVOList);
        return pageResult;
    }

    @Override
    public GenTableDetailDTO detail(Long id) {
        GenTable genTable = baseMapper.selectById(id);
        if (genTable == null) {
            throw new ServiceException(GenExceptionEnum.TABLE_DATA_NOT_EXISTS);
        }

        GenTableDetailDTO detailDTO = BeanUtil.copyProperties(genTable, GenTableDetailDTO.class);
        List<GenTableColumn> tableColumnList = genTableColumnService.lambdaQuery().eq(GenTableColumn::getTableId, id).list();
        List<GenTableColumnDetailDTO> columnDetailDTOList = BeanUtil.copyToList(tableColumnList, GenTableColumnDetailDTO.class);
        columnDetailDTOList.forEach(e -> e.setJavaFieldComment(e.getColumnComment()));
        detailDTO.setColumnList(columnDetailDTOList);
        return detailDTO;
    }

    @Override
    public void generateLocal(Long id) {
        GenTableDetailDTO detail = this.detail(id);
        try {
            codeGeneratorService.generateLocal(detail);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(GenExceptionEnum.CODE_GENERATE_FAIL);
        }
    }

    @Override
    public void generateDownload(Long id, HttpServletResponse response) {
        GenTableDetailDTO detail = this.detail(id);
        try {
            codeGeneratorService.generateDownload(detail, response);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException(GenExceptionEnum.CODE_GENERATE_FAIL);
        }
    }

    @Override
    public PageResult<GenTableVO> pageDbTable(BasePage basePage, GenTableQueryDTO queryDTO) {
        Integer pageNum = basePage.getPageNum();
        Integer pageSize = basePage.getPageSize();

        List<String> tableNameList = baseMapper.selectList(new LambdaQueryWrapper<>()).stream().map(GenTable::getTableName).collect(Collectors.toList());

        PageResult<GenTableVO> pageResult = new PageResult<>();
        pageResult.setPageNum(pageNum);
        pageResult.setPageSize(pageSize);

        String dbType = this.getDbType();
        if (dbType == null) {
            pageResult.setResults(List.of());
            return pageResult;
        }
        Page<GenTableBO> page = dbMapper.pageTable(new Page<>(pageNum, pageSize), dbType, queryDTO.getTableName(), tableNameList);

        pageResult.setPage(page.getPages());
        pageResult.setTotal(page.getTotal());
        List<GenTableVO> tableVOList = BeanUtil.copyToList(page.getRecords(), GenTableVO.class);
        pageResult.setResults(tableVOList);
        return pageResult;
    }

}
